package com.wuyou.securitydemo.component;

import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wuyou.sso.common.contant.CacheKey;
import com.wuyou.sso.common.excption.CustomException;
import com.wuyou.securitydemo.domain.TokenVO;
import com.wuyou.securitydemo.pwd.CustomizedPasswordAuthenticationToken;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * TokenStoreServiceImpl
 *
 * @Date 2025/4/11 10:43
 * @Author 无忧
 */
@Component
@Slf4j
public class CustomizedRedisTokenStore {

    @Autowired
    private RedisTemplate redisTemplate;

    private final static ObjectMapper mapper = new ObjectMapper();

    private RedisSerializer redisSerializer = new JdkSerializationRedisSerializer();

    private String prefix = "";
    private final static String TOKEN = "token";

    /**
     * 存储Token
     * @param authentication
     * @return
     */
    public void store(TokenVO tokenVO, Authentication authentication){
//        byte[] serializedAuthToken = serializeKey(generateKey(authentication));
        String key = tokenVO.getToken();
        byte[] serializedAuth = serialize(authentication);
        redisTemplate.opsForValue().set(key, serializedAuth);
    }

    /**
     * 获取TokenVO
     * @param authentication
     * @return
     */
    public TokenVO getToken(Authentication authentication){
        String key = "1:"+"sso_demo:" + TOKEN + StrUtil.COLON + generateKey(authentication) + StrUtil.COLON;

        Object o = redisTemplate.opsForValue().get(key);
        if (o != null) {
            TokenVO vo = (TokenVO) o;
            if(!vo.isActive()){
                this.remove(authentication);
                return null;
            }
            return vo;
        }
        return null;
    }

    /**
     *
     * @param token
     * @return
     */
    public TokenVO readAccessToken(String token){
        Object o = redisTemplate.opsForValue().get(extractKey(token));
        if (o == null) {
            return null;
        }
        return (TokenVO) o;
    }

    /**
     *
     * @param token
     * @return
     */
    public Authentication readAuthentication(String token){
        Object o = redisTemplate.opsForValue().get(extractKey(token));
        if (o == null) {
            return null;
        }
        return (Authentication) o;
    }

    private String generateKey(Authentication authentication) {
        try {
            Map<String, Object> values = new HashMap<>();
            values.put("username", authentication.getPrincipal());
            MessageDigest digest = MessageDigest.getInstance("MD5");
            byte[] bytes = digest.digest(values.toString().getBytes("UTF-8"));
            return String.format("%032x", new BigInteger(1, bytes));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * 根据用户名获取TokenVO
     * @param username 登录用户名
     * @return TokenVO
     */
    public Authentication getTokenByUsername(String username){
        String tokenKey = CacheKey.DEFAULT_TOKEN + username;
        String refreshKey = CacheKey.DEFAULT_REFRESH_TOKEN + username;
        CustomizedPasswordAuthenticationToken authentication = (CustomizedPasswordAuthenticationToken)redisTemplate.opsForValue().get(tokenKey);
        if (authentication == null) {
            authentication = (CustomizedPasswordAuthenticationToken)redisTemplate.opsForValue().get(refreshKey);
        }
        if(validate(authentication)){
            return authentication;
        }
        return null;
    }

    private String extractKey(String key){
        return "1:"+"sso_demo:" + key + StrUtil.COLON;
    }

    private byte[] serialize(Object object) {
        return redisSerializer.serialize(object);
    }

    private byte[] serializeKey(String object) {
        // token key type -> tenant:client:object
        return redisSerializer.serialize(extractKey(object));
    }
    private <T> T deserialize(byte[] bytes, Class<T> clazz) {
        Object deserialize = redisSerializer.deserialize(bytes);
        if (deserialize == null) {
            return null;
        }
        return (T) deserialize;
    }

    private boolean validate(CustomizedPasswordAuthenticationToken authentication){
        if (authentication == null) {
            return false;
        }
        refresh(authentication);
        return true;
    }

    private void storeToken(){

    }

    private void refresh(Authentication authentication){

    }



    static {
//        try {
            // 启用反序列化所需的类型信息,在属性中添加@class
//            mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL,
//                    JsonTypeInfo.As.PROPERTY);
//            mapper.registerModule(new CoreJackson2Module());
//            mapper.registerModule(new WebJackson2Module());
//            mapper.registerModule(new WebServletJackson2Module());
//            mapper.registerModule(new WebServerJackson2Module());
//            List<com.fasterxml.jackson.databind.Module> modules =
//                    SecurityJackson2Modules.getModules(loader);
//            mapper.registerModules(modules);
//        } catch (Exception e) {
//        }
    }

    /**
     * <p>
     * 根据用户账号获取所有的令牌
     * </p>
     * 按照令牌的过期时间点从小到到排列
     *
     * @param username 用户账号
     * @return 所有的令牌
     */
    
    public synchronized List<CustomizedPasswordAuthenticationToken> getAll(String username) {
        final Map<Object, Object> entries = this.token(username).entries();
        try {
            return entries.values().stream().filter(Objects::nonNull).map(v -> (CustomizedPasswordAuthenticationToken) v).collect(Collectors.toList());
        } catch (Exception e) {
        }
        return Collections.emptyList();

    }


    /**
     * 删除指定的令牌
     */
    public void remove(Authentication authentication) {
        remove(generateKey(authentication));
    }
    /**
     * 删除指定的令牌
     */
    public void remove(String token) {
        redisTemplate.delete(extractKey(token));
    }


    public CustomizedPasswordAuthenticationToken loadByTokenValue(String tokenValue) {
        Object val = this.tokenVal(tokenValue).get();
        return val instanceof CustomizedPasswordAuthenticationToken ? (CustomizedPasswordAuthenticationToken) val : null;
    }

    /**
     * 根据用户账号和设备id获取一个令牌
     *
     * @param username 用户账号
     * @param deviceId 设备id
     * @return 令牌
     */

    public synchronized CustomizedPasswordAuthenticationToken get(String username, String deviceId) {
        Object val = this.token(username).get(deviceId);
        return val instanceof CustomizedPasswordAuthenticationToken ? (CustomizedPasswordAuthenticationToken) val : null;
    }


    private synchronized BoundHashOperations<String, Object, Object> token(String key) {
        return redisTemplate.boundHashOps(new StringBuffer(CacheKey.DEFAULT_TOKEN).append(key).toString());
    }

    private synchronized BoundValueOperations<String, Object> tokenVal(String key) {
        return redisTemplate.boundValueOps(new StringBuffer(CacheKey.DEFAULT_TOKEN).append(key).toString());
    }

    /**
     * 检查令牌的内容合法性
     *
     * @param token 令牌
     * @throws CustomException 令牌非法
     */
    private void check(CustomizedPasswordAuthenticationToken token) {
        if (null == token) {
            throw new CustomException("令牌不能为空");
        }
        if (null == token.getName() || StringUtils.isBlank(token.getName())) {
            throw new CustomException("令牌中必须包含用户账号信息");
        }
        if (StringUtils.isBlank(token.getDeviceId())) {
            throw new CustomException("令牌中必须包含请求识别信息");
        }
    }

    public RedisTemplate<String, Object> getRedisTemplate() {
        return redisTemplate;
    }


    /**
     * 删除指定账号下所有的令牌
     *
     * @param authentication 用户认证信息
     */
    public void clearAll(Authentication authentication) {
        this.getAll(authentication.getName()).forEach(this::remove);
    }
}
